import view
from helper import invoke 
import parsingutils
import threading
import PIL.Image
import random
import cloth
import verlet
import constraint
import render
import sphere
import cube
import plane
import os
import copy

class SimulationController:
    
    def __init__(self):
        
        self._view = None
        self._commands = None
        self._commands_backup = None
        random.seed()
        
        self._time = 0
        self._time_of_next_command = 0
        self._time_step = 1
        self._cloth = None
        self._cloth_backup = None
        self._cloth_constraint = None
        self._cloth_constraint_backup = None
        self._obstacle_list = dict()
        self._simulation_id = -1
        self._out_dir = None
        self._frame_nbr = 0
        
        
        self._stabilization_iterations = 60
        self._stabilization_error = 0.0005 
        self._gravitation = 0.2
        self._air_friction = 0.5 # 0 < x < 1
        self._frame_size = (400, 400)
        self._frame_count = 20
        
        self._renderer_init = True
        self._InitThread()
    
    def SetView(self, view):
        self._view = view
    
    def _InvokeView(self, method, *args):
        invoke(self._view, method, args)
    
    def StartSimulation(self):
        if self._commands_backup == None:
            print "ERROR: No commands specified"
        elif self._cloth_backup == None or self._cloth_constraint_backup == None:
            print "ERROR: Simulation parameters are not defined"
        elif self._out_dir == None or self._out_dir == "":
            print "ERROR: Output directory is not defined"
        else:
            self._time = 0
            self._time_of_next_command = 0
            self._frame_nbr = 0
            self._simulation_id = random.randint(0, 99999)
            self._renderer_init = True
            self._obstacle_list = dict()
            self._commands = copy.deepcopy(self._commands_backup)
            self._cloth = copy.deepcopy(self._cloth_backup)
            self._cloth_constraint = copy.deepcopy(self._cloth_constraint_backup) 
            print "Simulation id: " + str(self._simulation_id)
            self._run_event.set()
            self._InvokeView(view.SimulationView.SetState, view.SimulationView.SIMULATION_STARTED)
    
    def StopSimulation(self):
        print "Stopping simulation..."
        self._InvokeView(view.SimulationView.SetState, view.SimulationView.SIMULATION_ABOUT_TO_STOP)
        self._run_event.clear()
     
    def MakeMovie(self):
        if self._simulation_id >= 0:
            self._InvokeView(view.SimulationView.SetState, view.SimulationView.SIMULATION_ABOUT_TO_STOP)
            in_file_name = 'img'+str(self._simulation_id)+'_*.bmp'
            out_file_name = 'movie'+str(self._simulation_id)+'.avi'
            cmd = "mencoder \"mf://"+in_file_name+"\" -mf fps=10 -o "+out_file_name+" -ovc lavc -lavcopts vcodec=msmpeg4v2:vbitrate=800"
            #print cmd
            dir = os.curdir;
            os.chdir(self._out_dir)
            os.system(cmd)
            os.chdir(dir)
            self._InvokeView(view.SimulationView.SetState, view.SimulationView.SIMULATION_STOPPED)
        else:
            print "ERROR: Perform simulation before creating movie"
        
    def SetClothParams(self, file_name):
        print "Paring file: "+file_name
        p = parsingutils.ClothParamsFileReader()
        if p.ReadParams(file_name):
            self._cloth_backup = cloth.Cloth(p.cloth_width, p.cloth_height, p.cloth_particle_distance)
            self._cloth_constraint_backup = constraint.ParticleSystemConstraints(p.cloth_width, p.cloth_height, p.cloth_stiffness_param1, \
                                                                          p.cloth_stiffness_param2, p.cloth_particle_distance)
            self._stabilization_iterations = p.stabilization_iterations
            self._stabilization_error = p.stabilization_error 
            self._gravitation = p.gravitation
            self._air_friction = p.air_friction # 0 < x < 1
            self._frame_size = (p.frame_width, p.frame_height)
            self._frame_count = p.frame_count
            print "Parsing successful"
            self._InvokeView(view.SimulationView.SetClothParamsFileName, file_name)
        else:
            print "ERROR: Parsing unsuccessful"
    
    def SetCommandList(self, file_name):
        print "Parsing file: "+file_name
        p = parsingutils.CommandsFileReader()
        if p.ReadCommands(file_name):
            self._commands_backup = p.Commands()
            print "Parsing successful"
            self._InvokeView(view.SimulationView.SetCommandsFileName, file_name)
        else:
            print "ERROR: Parsing unsuccessful"
    
    def SetOutDir(self, dir):
        self._out_dir = dir
        self._InvokeView(view.SimulationView.SetOutputDirectoryName, dir)
    
    def _InitThread(self):
        self._run_event = threading.Event()
        self._run_event.clear()
        self._simulator = threading.Thread(target = self._SimulateThread, name="Simulator")
        self._simulator.daemon = True
        self._simulator.start()
            
    def _InitRenderer(self):
        self._renderer = render.Renderer(self._frame_size)
        self._renderer.Register(sphere.Sphere, render.SphereRenderer())
        self._renderer.Register(cube.Cube, render.CubeRenderer())
        self._renderer.Register(plane.Plane, render.PlaneRenderer())
        self._renderer.Register(cloth.Cloth, render.ClothRenderer(self._cloth))
        #print "DEBUG: Init rendering"
        #self._renderer.Camera.Move((-3, 3, 8))
        #self._renderer.Camera.Rotate(elevation = -30, azimuth = 20)
    
    def _SimulateThread(self):
        while True:
            if not self._run_event.isSet():
                self._InvokeView(view.SimulationView.SetState, view.SimulationView.SIMULATION_STOPPED)
            self._run_event.wait()
            if self._renderer_init == True:
                self._InitRenderer()
                self._renderer_init = False
            self._Simulate()
            
    def _Simulate(self):
        if self._time - self._time_of_next_command < self._time_step:
            while len(self._commands) > 0:
                command = self._commands.pop(0)
                wait =  self._ExecuteCommand(command)
                if wait:
                    break
        self._time += self._time_step
        
        p, self._cloth.prev_points = verlet.Verlet.Integrate(self._cloth.points, self._cloth.prev_points, \
                                                                              self._air_friction, (0, -self._gravitation, 0))        
        y = p.shape[0]
        x = p.shape[1]
          
        for i in range(self._stabilization_iterations):
            for obstacle in self._obstacle_list.values():
                for i in range(y):
                    for j in range(x):
                        p[i,j,:] = obstacle.CollisionHandle(p[i,j,:])
                
            p = self._cloth_constraint.DistanceConstraints(p)
            if self._cloth_constraint.ConstraintsError() < self._stabilization_error:
                break
            
        for obstacle in self._obstacle_list.values():
            for i in range(y):
                for j in range(x):
                    p[i,j,:] = obstacle.CollisionHandle(p[i,j,:])
        
        self._cloth.points = p
        
        self._renderer.AppendToRenderList(self._cloth)
        self._renderer.AppendToRenderList(self._obstacle_list.values())
        self._renderer.Render()
        self._renderer.ClearRenderList()
        self._SaveFrame(self._renderer.output[0], self._frame_nbr, self._renderer.output[1])
        
        
        self._InvokeView(view.SimulationView.SetMinorTitle, self._frame_nbr)
        self._frame_nbr += 1
        if self._frame_nbr == self._frame_count:
            self.StopSimulation()
            #sys.exit()
        #    self.STOP.set()
        
    def _ExecuteCommand(self, command):
        speed = 1
        object = command.object
        operation = command.operation
        if operation._name == "wait":
            try:
                dt = float(operation("t").GetValue())
                if dt < 0:
                    print "ERROR: \'t\' must not be less than 0"
                    return False
                else:
                    self._time_of_next_command = self._time + dt
                return True
            except:
                print "ERROR: \'t\' must be float"
                return False
            
        elif operation._name == "move":
            try:
                x = float(operation("x").GetValue())
                y = float(operation("y").GetValue())
                z = float(operation("z").GetValue())
                if object._name == "object":
                    id = object("id").GetValue()
                    if id in self._obstacle_list:
                        self._obstacle_list[id].Move(x, y, z, speed)
                    else:
                        print "ERROR: Object "+str(id)+" does not exist"
                else:
                    self._renderer.Camera.Move((x, y, z))
            except:
                print "ERROR: Cannot parse one of the attributes"
        elif operation._name == "rotate":
            try:
                x = float(operation("x").GetValue())
                y = float(operation("y").GetValue())
                z = float(operation("z").GetValue())
                alpha = float(operation("alpha").GetValue())
                if object._name == "object":
                    id = object("id").GetValue()
                    if id in self._obstacle_list:
                        self._obstacle_list[id].Rotate(x, y, z, alpha)
                    else:
                        print "ERROR: Object "+str(id)+" does not exist"
                else:
                    if x == 0 and y == 0 and z == 1:
                        self._renderer.Camera.Rotate(spin = alpha)
                    elif x == 0 and y == 1 and z == 0:
                        self._renderer.Camera.Rotate(azimuth = alpha)
                    elif x == 1 and y == 0 and z == 0:
                        self._renderer.Camera.Rotate(elevation = alpha)
                    else:
                        print "ERROR: Axis ("+str(x)+","+str(y)+","+str(z)+") is not supported"                   
            except:
                print "ERROR: Cannot parse one of the attributes"
        elif operation._name == "scale":
            try:
                if object._name == "object":
                    id = object("id").GetValue()
                    s = float(operation("s").GetValue())
                    if s <= 0:
                        print "ERROR: \'s\' must be positive"
                    elif id in self._obstacle_list:
                        self._obstacle_list[id].Resize(s, speed) 
                    else:
                        print "ERROR: Object "+str(id)+" does not exist"
                else:
                    print "ERROR: Cannot scale camera"
            except:
                print "ERROR: Cannot parse \'s\' attributes"
        elif operation._name == "delete":
            if object._name == "object":
                id = object("id").GetValue()
                if id not in self._obstacle_list:
                    print "ERROR: Object "+str(id)+" does not exist"
                else:
                    del self._obstacle_list[id]            
            else:
                print "ERROR: Cannot delete camera"
        elif operation._name == "create":
            try:
                type = operation("type").GetValue()
                size = float(operation("size").GetValue()) 
                x = float(operation("x").GetValue())
                y = float(operation("y").GetValue())
                z = float(operation("z").GetValue())
                if object._name == "object":
                    id = object("id").GetValue() 
                    if id in self._obstacle_list:
                        print "ERROR: Object "+str(id)+" already exists"
                    else:
                        if type=="sphere":
                            self._obstacle_list[id] = sphere.Sphere(x, y, z, size)
                        elif type=="cube":
                            self._obstacle_list[id] = cube.Cube(x, y, z, size)
                        elif type=="plane":
                            self._obstacle_list[id] = plane.Plane(x, y, z, 0, 1, 0, size)
                        else:
                            print "ERROR: Object of type "+str(type)+" cannot be created"
                        
                        pass              
                else:
                    print "ERROR: Cannot create camera"
            except:
                print "ERROR: Cannot parse one of the attributes"
        return False
        
    def _SaveFrame(self, frame, frame_nbr, size):
        img = PIL.Image.fromstring('RGB', size, frame, 'raw', 'RGB', 0, 1)
        img = img.transpose(PIL.Image.FLIP_TOP_BOTTOM)
        if frame_nbr < 10:
            str_frame_nbr = "0"
        else:
            str_frame_nbr = ""
        str_frame_nbr += str(frame_nbr)
        img.save(self._out_dir+'\\img'+str(self._simulation_id)+'_'+str_frame_nbr+'.bmp', 'BMP')
        
if __name__ == "__main__":
    c = SimulationController()

    s = cloth.Cloth(20, 20, 0.25)

    cc = constraint.ParticleSystemConstraints(20, 20, 3, 1, 0.25)
    s2 = sphere.Sphere(0, -2.1, 0, 2)
    c._cloth = s
    c._cloth_constraint = cc
    c._obstacle_list.append(s2)
    c.STOP = threading.Event()
    c.STOP.clear()
    c.StartSimulation()
    
    c.STOP.wait()      